#include "parafluidtriangle.h"

#include <cmath>

using namespace ParaFluid;

Triangle::~Triangle()
{
	/* does nothing */
}

Triangle::Triangle()
	: _point1()
	, _point2()
	, _point3()
	, _normalComputed(false)
	, _normal()
	, _degenerate(false)
{
	/* does nothing */
}

Triangle::Triangle(const Triangle & other)
	: _point1(other._point1)
	, _point2(other._point2)
	, _point3(other._point3)
	, _normalComputed(false)
	, _normal()
	, _degenerate(false)
{
	computeNormal();
}

Triangle::Triangle(const Vector & point1, const Vector & point2, const Vector & point3)
	: _point1(point1)
	, _point2(point2)
	, _point3(point3)
	, _normalComputed(false)
	, _normal()
	, _degenerate(false)
{
	computeNormal();
}

Triangle & Triangle::operator= (const Triangle & rhs)
{
	if(&rhs != this)
	{
		_point1 = rhs._point1;
		_point2 = rhs._point2;
		_point3 = rhs._point3;
		_normalComputed = rhs._normalComputed;
		_normal = rhs._normal;
		_degenerate = rhs._degenerate;
	}
	return *this;
}

const Vector & Triangle::point1() const
{
	return _point1;
}

const Vector & Triangle::point2() const
{
	return _point2;
}

const Vector & Triangle::point3() const
{
	return _point3;
}

const Vector & Triangle::normal() const
{
	computeNormal();
	
	return _normal;
}

bool Triangle::isDegenerate() const
{
	computeNormal();
	
	return _degenerate;
}

enum Side
{
	Positive,
	Negative,
	Parallel
};

Side side(const Vector & pointTested, const Vector & triangleNormal, const Vector & trianglePointA, const Vector & trianglePointB)
{
	Vector crossProduct = (pointTested - trianglePointA) ^ (trianglePointB - trianglePointA);
        
	float projection = crossProduct * triangleNormal;
        
	const float EPSILON = 0.00000000001f;
        
	if(fabs(projection) <= EPSILON)
	{
		return Parallel;
	}
        else if(projection >= 0)
	{
		return Positive;
	}
	else
	{
		return Negative;
	}
}

bool pointInTriangle(const Vector & pointTested, const Vector & triangleNormal, const Vector & trianglePoint1, const Vector & trianglePoint2, const Vector & trianglePoint3)
{
	Side side1 = side(pointTested, triangleNormal, trianglePoint1, trianglePoint2);
	Side side2 = side(pointTested, triangleNormal, trianglePoint2, trianglePoint3);
	Side side3 = side(pointTested, triangleNormal, trianglePoint3, trianglePoint1);
	if(side1 == Parallel)
	{
		return side2 == side3;
	}
	else if(side2 == Parallel)
	{
		return side1 == side3;
	}
	else if(side3 == Parallel)
	{
		return side1 == side2;
	}
	else
	{
		return side1 == side2 && side2 == side3;
	}
}

bool Triangle::intersects(const Segment & segment, Vector & intersectionPoint) const
{
	bool result = false;
	
	const float EPSILON = 0.00000000001f;
	
	computeNormal();
	
	// Compute only if triangle is not degenerate
	if(!_degenerate)
	{
		Vector direction = segment.pointB - segment.pointA;
		
		if(direction.length() > 0)
		{
			float denom = _normal * direction;
			
			if(fabs(denom) > EPSILON)
			{
				float nom = _normal * (_point1 - segment.pointA);
				
				float r = nom / denom;
				
				if(0.0f <= r && r <= 1.0f)
				{
					intersectionPoint = segment.pointA + (direction * r);
					
					if(pointInTriangle(intersectionPoint, _normal, _point1, _point2, _point3))
					{
						result = true;
					}
				}
			}
		}
	}
	
	return result;
}

void Triangle::computeNormal() const
{
	if(!_normalComputed)
	{
		_normalComputed = true;
		_normal = (_point2 - _point1) ^ (_point3 - _point1);
		_normal.unitary();
		_degenerate = _normal.length() == 0;
	}
}
